﻿using System;
using System.Reflection;
using System.Reflection.Emit;

namespace NForth3
{
    class Compiler
    {
        public Type ClassType { set; get; }
        public FieldInfo AssemblyField { set; get; }
        public FieldInfo ModuleField { set; get; }
        public FieldInfo TypeField { set; get; }
        public FieldInfo MethodField { set; get; }
        public FieldInfo IdField { set; get; }

        public Compiler(NForth nforth)
        {
            CreateType(nforth);
        }

        private void CreateType(NForth nforth)
        {
            ModuleBuilder module = nforth.Assembly.DefineDynamicModule("Compiler");
            TypeBuilder type = module.DefineType("Compiler", TypeAttributes.Public);

            // fields
            FieldBuilder assembly_field = type.DefineField("Assembly", typeof(AssemblyBuilder), FieldAttributes.Static | FieldAttributes.Public);
            FieldBuilder module_field = type.DefineField("Module", typeof(ModuleBuilder), FieldAttributes.Static | FieldAttributes.Public);
            FieldBuilder type_field = type.DefineField("Type", typeof(TypeBuilder), FieldAttributes.Static | FieldAttributes.Public);
            FieldBuilder method_field = type.DefineField("Method", typeof(MethodBuilder), FieldAttributes.Static | FieldAttributes.Public);
            FieldBuilder id_field = type.DefineField("Id", typeof(uint), FieldAttributes.Static | FieldAttributes.Public);

            // create method
            MethodBuilder create = type.DefineMethod("Create", MethodAttributes.Public | MethodAttributes.Static);
            ILGenerator il = create.GetILGenerator();
            il.DeclareLocal(typeof(string));
            // id_field++
            il.Emit(OpCodes.Ldsfld, id_field);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stsfld, id_field);
            // loc0 = id_field.ToString()
            il.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
            il.Emit(OpCodes.Stloc_0);
            // Module = Assembly.DefineDynamicModule("Module" + loc0);
            il.Emit(OpCodes.Ldsfld, assembly_field);
            il.Emit(OpCodes.Ldstr, "Module");
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }));
            il.Emit(OpCodes.Call, typeof(AssemblyBuilder).GetMethod("DefineDynamicModule", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Stsfld, module_field);
            // Type = Assembly.DefineType("Type" + loc0);
            il.Emit(OpCodes.Ldsfld, assembly_field);
            il.Emit(OpCodes.Ldstr, "Type");
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }));
            il.Emit(OpCodes.Ldsfld, typeof(TypeAttributes).GetField("Public"));
            il.Emit(OpCodes.Call, typeof(ModuleBuilder).GetMethod("DefineType", new Type[] { typeof(string), typeof(TypeAttributes) }));
            il.Emit(OpCodes.Stsfld, type_field);
            // Method = Type.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.Static, null, null);
            il.Emit(OpCodes.Ldsfld, type_field);
            il.Emit(OpCodes.Ldstr, "Execute");
            il.Emit(OpCodes.Ldsfld, typeof(MethodAttributes).GetField("Public"));
            il.Emit(OpCodes.Ldsfld, typeof(MethodAttributes).GetField("Static"));
            il.Emit(OpCodes.Or);
            il.Emit(OpCodes.Call, typeof(TypeBuilder).GetMethod("DefineMethod", new Type[] { typeof(string), typeof(MethodAttributes) }));
            il.Emit(OpCodes.Stsfld, method_field);
            il.Emit(OpCodes.Ret);

            MethodBuilder finish = type.DefineMethod("Finish", MethodAttributes.Public | MethodAttributes.Static, typeof(Type), null);
            il = finish.GetILGenerator();
            // Type.CreateType()
            il.Emit(OpCodes.Ldsfld, type_field);
            il.Emit(OpCodes.Call, typeof(TypeBuilder).GetMethod("CreateType"));
            // Module.CreateGlobalFunctions()
            il.Emit(OpCodes.Ldsfld, module_field);
            il.Emit(OpCodes.Call, typeof(ModuleBuilder).GetMethod("CreateGlobalFunctions"));
            // return Module.GetType("Type" + Id.ToString());
            il.Emit(OpCodes.Ldsfld, module_field);
            il.Emit(OpCodes.Ldstr, "Type");
            il.Emit(OpCodes.Ldsfld, id_field);
            il.Emit(OpCodes.Callvirt, typeof(Object).GetMethod("ToString"));
            il.Emit(OpCodes.Call, typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }));
            il.Emit(OpCodes.Call, typeof(ModuleBuilder).GetMethod("GetType", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Ret);

            type.CreateType();
            module.CreateGlobalFunctions();

            ClassType = module.GetType("Compiler");
            AssemblyField = ClassType.GetField("Assembly");
            ModuleField = ClassType.GetField("Module");
            MethodField = ClassType.GetField("Type");
            MethodField = ClassType.GetField("Method");
            IdField = ClassType.GetField("Id");
        }
    }
}
